import Koa from 'koa'
import jwt from 'jsonwebtoken'
import { Models } from '../common/typings/model'
import { Menu } from '../common/typings/menu'
import config from '../config/Config'
import { Forbbiden, AuthFailed } from '../core/HttpException'
import Mysql from '../core/Mysql'
import RedisClient from '../core/Redis'
import { getTreeByList } from '../common/utils/utils'
interface Decode {
  scope: number
  uid: number
}

interface Role {
  id: number
  menuList: Menu.Menu[]
  parentId: number
  name: string
}

export default class Auth {
  /**
   * 校验token是否存在
   * @param ctx
   * @param next
   * @param callback
   */
  static async verifyToken(ctx: Models.Ctx, next: Koa.Next, callback?: Function) {
    //检测token是否合法
    const userToken = Auth.getToken(ctx)
    let errMsg = 'token不合法'
    if (!userToken) {
      throw new Forbbiden('无访问权限')
    }
    const uid = (await RedisClient.getTokenValue(userToken)).results
    let decode
    try {
      decode = jwt.verify(userToken, config.security.secretKey) as string | Decode
    } catch (error) {
      if (error && (error as Error)?.name == 'TokenExpiredError') {
        errMsg = 'token已过期'
        throw new AuthFailed(errMsg)
      }
      throw new Forbbiden(errMsg)
    }
    if (typeof decode === 'object') {
      if (uid != decode.uid) {
        errMsg = 'token已过期'
        throw new AuthFailed(errMsg, 401)
      }
      // 保存uid和scope
      ctx.auth = {
        uid: decode.uid,
        scope: decode.scope,
      }
      if (callback) {
        await callback(decode)
      }
    } else {
      errMsg = '权限不足'
      throw new Forbbiden(errMsg)
    }
    await next()
  }

  /**
   * 获取token
   * @param ctx
   * @returns
   */
  static getToken(ctx: Models.Ctx) {
    return ctx.header['authorization'] || ctx.cookies.get('authorization')
  }

  /**
   * 校验token权限
   * @param ctx
   * @param next
   */
  static async verifyTokenPermission(ctx: Models.Ctx, next: Koa.Next) {
    await Auth.verifyToken(ctx, next, async (decode: Decode) => {
      const permissionList: string[] = await Auth.getRedisUserPermission(decode)

      const bool = permissionList.find((permission) => {
        // const path = ctx.path
        const path = `${config.api_prefix}${permission.split(':').join('/')}`
        return path === ctx.path
      })
      if (!bool) {
        throw new Forbbiden('权限不足')
      }
    })
  }

  static getUserId(token: string) {
    // const data = jwt.decode(token, {secret: config.security.secretKey})
    const data = jwt.verify(token, config.security.secretKey) as Decode
    if (typeof data === 'object' && !data.uid) {
      const errMsg = 'token已过期'
      throw new Forbbiden(errMsg)
    } else {
      return data.uid
    }
  }

  /**
   * 校验用户名是否可用
   * @param userName
   * @returns
   */
  static async verifyUserNameExistence(userName: string): Promise<boolean> {
    return new Promise(async (resolve, reject) => {
      const res: Models.Result = await Mysql.command(`
                SELECT
                    user_name
                FROM
                    user
                where
                    user.user_name = '${userName}'
            `)
      if (!res.error) {
        const user = res.results[0]
        if (!user) {
          resolve(false)
        } else {
          resolve(true)
        }
      } else {
        reject()
      }
    })
  }

  /**
   * 获取用户权限
   * @param decode
   * @returns
   */
  static async getUserPermission(decode: Decode): Promise<Menu.Menu[]> {
    const { scope } = decode
    return new Promise(async (resolve, reject) => {
      let res: Models.Result

      try {
        res = await Mysql.command(`
            SELECT
              permissions
            FROM
              role
            where
              id = ${scope}
        `)
        if (!res.error) {
          const user = res.results[0]
          if (user) {
            const menuList: Menu.Menu[] = (
              await Mysql.command(`
                SELECT
                  permission
                FROM
                  menu
                WHERE
                  FIND_IN_SET(
                  id,
                  '${user.permissions}')
              `)
            ).results
            resolve(menuList)
          } else {
            resolve([])
          }
        } else {
          reject()
        }
      } catch (error) {
        console.log(error)
        reject()
      }
    })
  }

  /**
   * 校验用户权限
   * @param decode
   * @returns
   */
  static async getRedisUserPermission(decode: Decode): Promise<string[]> {
    const { scope } = decode
    return new Promise(async (resolve, reject) => {
      RedisClient.redis.select(1).then(() => {
        RedisClient.redis.keys('*').then(async (res) => {
          Promise.all(res.map((item) => RedisClient.redis.hgetall(item))).then((result) => {
            const allRoleList = result.map((item) => {
              return {
                ...item,
                id: Number(item.id),
                parentId: Number(item.parentId),
              }
            })
            const roleTree = getTreeByList(allRoleList, 0)
            // 过滤, 获取当前角色及当前角色的祖先角色的所有记录
            const roleList: Models.TreeNode[] = []
            const each = (list: Models.TreeNode[], nodeId: number) => {
              const arr = list.filter((item) => item.id === nodeId)
              if (arr.length) {
                roleList.push(...arr)
                each(list, arr[0].parentId)
              }
            }
            each(roleTree, scope)
            // 当前角色有权限的所有权限.
            let permissionList: string[] = []
            const merge = (list: Models.TreeNode[]) => {
              list.forEach((item) => {
                permissionList = [...new Set([...permissionList, ...item.permissions.split(',')])]
                if (item.children) {
                  merge(item.children)
                }
              })
            }
            // 合并当前角色和当前角色的祖先角色的所有菜单
            merge(roleTree)
            resolve(permissionList)
          })
        })
      })
    })
  }

  /**
   * 获取所有角色的权限列表
   * @returns
   */
  static async getAllRolePermission(): Promise<Role[]> {
    return new Promise(async (resolve, reject) => {
      let res: Models.Result
      try {
        res = await Mysql.command(`
            SELECT
              id,
              permissions,
              parent_id parentId,
              name
            FROM
              role
        `)
        if (!res.error) {
          const menuList: Role[] = []
          for (let i = 0; i < res.results.length; i++) {
            const item: Menu.Menu = res.results[i]
            menuList.push({
              id: item.id,
              parentId: item.parentId,
              name: item.name,
              menuList: await Auth.getUserPermission({
                scope: item.id,
                uid: 0,
              }),
            })
          }
          resolve(menuList)
        } else {
          reject()
        }
      } catch (error) {
        console.log(error)
        reject()
      }
    })
  }

  /**
   * 更新redis里的角色
   */
  static updateRedisRole() {
    Auth.getAllRolePermission().then((list) => {
      list.forEach((res) => {
        if (res.menuList.length > 0) {
          RedisClient.updateRoles(
            (res.id || '').toString(),
            new Map([
              ['id', res.id.toString()],
              ['parentId', res.parentId.toString()],
              ['permissions', res.menuList.map((item) => item.permission).join(',')],
            ])
          )
        }
      })
    })
  }
}
